From 9f98208d5e42c6d832fc0f309b02f157f4453973 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 12 Aug 2016 00:47:49 +0300 Subject: [PATCH] Add --message-format flag. --- src/bin/bench.rs | 7 ++- src/bin/build.rs | 6 +++ src/bin/doc.rs | 6 +++ src/bin/install.rs | 1 + src/bin/run.rs | 6 +++ src/bin/rustc.rs | 6 +++ src/bin/rustdoc.rs | 6 +++ src/bin/test.rs | 7 +++ src/cargo/ops/cargo_compile.rs | 22 +++++++++- src/cargo/ops/cargo_package.rs | 1 + src/cargo/ops/cargo_rustc/mod.rs | 43 ++++++++++++++++-- src/cargo/ops/mod.rs | 2 +- tests/build.rs | 75 ++++++++++++++++++++++++++++++++ tests/cargotest/support/mod.rs | 31 ++++++++----- 14 files changed, 201 insertions(+), 18 deletions(-) diff --git a/src/bin/bench.rs b/src/bin/bench.rs index f2d028d34..521c87c2f 100644 --- a/src/bin/bench.rs +++ b/src/bin/bench.rs @@ -16,6 +16,7 @@ pub struct Options { flag_verbose: u32, flag_quiet: Option, flag_color: Option, + flag_message_format: Option, flag_lib: bool, flag_bin: Vec, flag_example: Vec, @@ -50,6 +51,7 @@ Options: -v, --verbose ... Use verbose output -q, --quiet No output printed to stdout --color WHEN Coloring: auto, always, never + --message-format FMT Error format: human, json-v1 --frozen Require Cargo.lock and cache are up to date --locked Require Cargo.lock is up to date @@ -75,7 +77,9 @@ pub fn execute(options: Options, config: &Config) -> CliResult> { &options.flag_color, options.flag_frozen, options.flag_locked)); - + let message_format = try!(ops::MessageFormat::from_option( + &options.flag_message_format + )); let ops = ops::TestOptions { no_run: options.flag_no_run, no_fail_fast: false, @@ -96,6 +100,7 @@ pub fn execute(options: Options, config: &Config) -> CliResult> { &options.flag_test, &options.flag_example, &options.flag_bench), + message_format: message_format, target_rustdoc_args: None, target_rustc_args: None, }, diff --git a/src/bin/build.rs b/src/bin/build.rs index 2f23ce1be..c4619f541 100644 --- a/src/bin/build.rs +++ b/src/bin/build.rs @@ -18,6 +18,7 @@ pub struct Options { flag_verbose: u32, flag_quiet: Option, flag_color: Option, + flag_message_format: Option, flag_release: bool, flag_lib: bool, flag_bin: Vec, @@ -52,6 +53,7 @@ Options: -v, --verbose ... Use verbose output -q, --quiet No output printed to stdout --color WHEN Coloring: auto, always, never + --message-format FMT Error format: human, json-v1 --frozen Require Cargo.lock and cache are up to date --locked Require Cargo.lock is up to date @@ -73,6 +75,9 @@ pub fn execute(options: Options, config: &Config) -> CliResult> { &options.flag_color, options.flag_frozen, options.flag_locked)); + let message_format = try!(ops::MessageFormat::from_option( + &options.flag_message_format + )); let root = try!(find_root_manifest_for_wd(options.flag_manifest_path, config.cwd())); @@ -92,6 +97,7 @@ pub fn execute(options: Options, config: &Config) -> CliResult> { &options.flag_test, &options.flag_example, &options.flag_bench), + message_format: message_format, target_rustdoc_args: None, target_rustc_args: None, }; diff --git a/src/bin/doc.rs b/src/bin/doc.rs index c4139987c..83431ef66 100644 --- a/src/bin/doc.rs +++ b/src/bin/doc.rs @@ -17,6 +17,7 @@ pub struct Options { flag_verbose: u32, flag_quiet: Option, flag_color: Option, + flag_message_format: Option, flag_package: Vec, flag_lib: bool, flag_bin: Vec, @@ -47,6 +48,7 @@ Options: -v, --verbose ... Use verbose output -q, --quiet No output printed to stdout --color WHEN Coloring: auto, always, never + --message-format FMT Error format: human, json-v1 --frozen Require Cargo.lock and cache are up to date --locked Require Cargo.lock is up to date @@ -65,6 +67,9 @@ pub fn execute(options: Options, config: &Config) -> CliResult> { &options.flag_color, options.flag_frozen, options.flag_locked)); + let message_format = try!(ops::MessageFormat::from_option( + &options.flag_message_format + )); let root = try!(find_root_manifest_for_wd(options.flag_manifest_path, config.cwd())); @@ -85,6 +90,7 @@ pub fn execute(options: Options, config: &Config) -> CliResult> { &empty, &empty, &empty), + message_format: message_format, release: options.flag_release, mode: ops::CompileMode::Doc { deps: !options.flag_no_deps, diff --git a/src/bin/install.rs b/src/bin/install.rs index 544b115e9..b5d67fd41 100644 --- a/src/bin/install.rs +++ b/src/bin/install.rs @@ -114,6 +114,7 @@ pub fn execute(options: Options, config: &Config) -> CliResult> { release: !options.flag_debug, filter: ops::CompileFilter::new(false, &options.flag_bin, &[], &options.flag_example, &[]), + message_format: ops::MessageFormat::Human, target_rustc_args: None, target_rustdoc_args: None, }; diff --git a/src/bin/run.rs b/src/bin/run.rs index 5f9843c83..4b03b53bc 100644 --- a/src/bin/run.rs +++ b/src/bin/run.rs @@ -16,6 +16,7 @@ pub struct Options { flag_verbose: u32, flag_quiet: Option, flag_color: Option, + flag_message_format: Option, flag_release: bool, flag_frozen: bool, flag_locked: bool, @@ -42,6 +43,7 @@ Options: -v, --verbose ... Use verbose output -q, --quiet No output printed to stdout --color WHEN Coloring: auto, always, never + --message-format FMT Error format: human, json-v1 --frozen Require Cargo.lock and cache are up to date --locked Require Cargo.lock is up to date @@ -61,6 +63,9 @@ pub fn execute(options: Options, config: &Config) -> CliResult> { &options.flag_color, options.flag_frozen, options.flag_locked)); + let message_format = try!(ops::MessageFormat::from_option( + &options.flag_message_format + )); let root = try!(find_root_manifest_for_wd(options.flag_manifest_path, config.cwd())); @@ -91,6 +96,7 @@ pub fn execute(options: Options, config: &Config) -> CliResult> { bins: &bins, examples: &examples, } }, + message_format: message_format, target_rustdoc_args: None, target_rustc_args: None, }; diff --git a/src/bin/rustc.rs b/src/bin/rustc.rs index e81c8d18f..2743c3669 100644 --- a/src/bin/rustc.rs +++ b/src/bin/rustc.rs @@ -19,6 +19,7 @@ pub struct Options { flag_verbose: u32, flag_quiet: Option, flag_color: Option, + flag_message_format: Option, flag_release: bool, flag_lib: bool, flag_bin: Vec, @@ -55,6 +56,7 @@ Options: -v, --verbose ... Use verbose output -q, --quiet No output printed to stdout --color WHEN Coloring: auto, always, never + --message-format FMT Error format: human, json-v1 --frozen Require Cargo.lock and cache are up to date --locked Require Cargo.lock is up to date @@ -80,6 +82,9 @@ pub fn execute(options: Options, config: &Config) -> CliResult> { &options.flag_color, options.flag_frozen, options.flag_locked)); + let message_format = try!(ops::MessageFormat::from_option( + &options.flag_message_format + )); let root = try!(find_root_manifest_for_wd(options.flag_manifest_path, config.cwd())); @@ -110,6 +115,7 @@ pub fn execute(options: Options, config: &Config) -> CliResult> { &options.flag_test, &options.flag_example, &options.flag_bench), + message_format: message_format, target_rustdoc_args: None, target_rustc_args: options.arg_opts.as_ref().map(|a| &a[..]), }; diff --git a/src/bin/rustdoc.rs b/src/bin/rustdoc.rs index 15e4a91ca..79e368090 100644 --- a/src/bin/rustdoc.rs +++ b/src/bin/rustdoc.rs @@ -17,6 +17,7 @@ pub struct Options { flag_release: bool, flag_quiet: Option, flag_color: Option, + flag_message_format: Option, flag_package: Option, flag_lib: bool, flag_bin: Vec, @@ -52,6 +53,7 @@ Options: -v, --verbose ... Use verbose output -q, --quiet No output printed to stdout --color WHEN Coloring: auto, always, never + --message-format FMT Error format: human, json-v1 --frozen Require Cargo.lock and cache are up to date --locked Require Cargo.lock is up to date @@ -74,6 +76,9 @@ pub fn execute(options: Options, config: &Config) -> CliResult> { &options.flag_color, options.flag_frozen, options.flag_locked)); + let message_format = try!(ops::MessageFormat::from_option( + &options.flag_message_format + )); let root = try!(find_root_manifest_for_wd(options.flag_manifest_path, config.cwd())); @@ -95,6 +100,7 @@ pub fn execute(options: Options, config: &Config) -> CliResult> { &options.flag_test, &options.flag_example, &options.flag_bench), + message_format: message_format, mode: ops::CompileMode::Doc { deps: false }, target_rustdoc_args: Some(&options.arg_opts), target_rustc_args: None, diff --git a/src/bin/test.rs b/src/bin/test.rs index c5e687a64..a2f1f5f12 100644 --- a/src/bin/test.rs +++ b/src/bin/test.rs @@ -23,6 +23,7 @@ pub struct Options { flag_verbose: u32, flag_quiet: Option, flag_color: Option, + flag_message_format: Option, flag_release: bool, flag_no_fail_fast: bool, flag_frozen: bool, @@ -55,6 +56,7 @@ Options: -v, --verbose ... Use verbose output -q, --quiet No output printed to stdout --color WHEN Coloring: auto, always, never + --message-format FMT Error format: human, json-v1 --no-fail-fast Run all tests regardless of failure --frozen Require Cargo.lock and cache are up to date --locked Require Cargo.lock is up to date @@ -92,6 +94,10 @@ pub fn execute(options: Options, config: &Config) -> CliResult> { &options.flag_color, options.flag_frozen, options.flag_locked)); + let message_format = try!(ops::MessageFormat::from_option( + &options.flag_message_format + )); + let root = try!(find_root_manifest_for_wd(options.flag_manifest_path, config.cwd())); let empty = Vec::new(); @@ -124,6 +130,7 @@ pub fn execute(options: Options, config: &Config) -> CliResult> { release: options.flag_release, mode: mode, filter: filter, + message_format: message_format, target_rustdoc_args: None, target_rustc_args: None, }, diff --git a/src/cargo/ops/cargo_compile.rs b/src/cargo/ops/cargo_compile.rs index 512117ed8..3b88365d5 100644 --- a/src/cargo/ops/cargo_compile.rs +++ b/src/cargo/ops/cargo_compile.rs @@ -59,6 +59,8 @@ pub struct CompileOptions<'a> { pub release: bool, /// Mode for this compile. pub mode: CompileMode, + /// `--error_format` flag for the compiler. + pub message_format: MessageFormat, /// Extra arguments to be passed to rustdoc (for main crate and dependencies) pub target_rustdoc_args: Option<&'a [String]>, /// The specified target will be compiled with all the available arguments, @@ -74,6 +76,23 @@ pub enum CompileMode { Doc { deps: bool }, } +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum MessageFormat { + Human, + Json +} + +impl MessageFormat { + pub fn from_option(opt: &Option) -> CargoResult { + match opt.as_ref().map(|s| s.as_ref()) { + None | Some("human") => Ok(MessageFormat::Human), + Some("json-v1") => Ok(MessageFormat::Json), + Some(other) => bail!("argument for --message-format must be human or json-v1, \ + but found `{}`", other) + } + } +} + pub enum CompileFilter<'a> { Everything, Only { @@ -150,7 +169,7 @@ pub fn compile_ws<'a>(ws: &Workspace<'a>, let root_package = try!(ws.current()); let CompileOptions { config, jobs, target, spec, features, all_features, no_default_features, - release, mode, + release, mode, message_format, ref filter, ref exec_engine, ref target_rustdoc_args, ref target_rustc_args } = *options; @@ -242,6 +261,7 @@ pub fn compile_ws<'a>(ws: &Workspace<'a>, build_config.exec_engine = exec_engine.clone(); build_config.release = release; build_config.test = mode == CompileMode::Test; + build_config.json_errors = message_format == MessageFormat::Json; if let CompileMode::Doc { deps } = mode { build_config.doc_all = deps; } diff --git a/src/cargo/ops/cargo_package.rs b/src/cargo/ops/cargo_package.rs index dc12e46ff..2001c6c87 100644 --- a/src/cargo/ops/cargo_package.rs +++ b/src/cargo/ops/cargo_package.rs @@ -295,6 +295,7 @@ fn run_verify(ws: &Workspace, tar: &File, opts: &PackageOpts) -> CargoResult<()> filter: ops::CompileFilter::Everything, exec_engine: None, release: false, + message_format: ops::MessageFormat::Human, mode: ops::CompileMode::Build, target_rustdoc_args: None, target_rustc_args: None, diff --git a/src/cargo/ops/cargo_rustc/mod.rs b/src/cargo/ops/cargo_rustc/mod.rs index 59d1ec57f..dac09a7ea 100644 --- a/src/cargo/ops/cargo_rustc/mod.rs +++ b/src/cargo/ops/cargo_rustc/mod.rs @@ -5,6 +5,8 @@ use std::fs; use std::path::{self, PathBuf}; use std::sync::Arc; +use rustc_serialize::json; + use core::{Package, PackageId, PackageSet, Target, Resolve}; use core::{Profile, Profiles, Workspace}; use core::shell::ColorConfig; @@ -44,6 +46,7 @@ pub struct BuildConfig { pub release: bool, pub test: bool, pub doc_all: bool, + pub json_errors: bool, } #[derive(Clone, Default)] @@ -212,7 +215,6 @@ fn rustc(cx: &mut Context, unit: &Unit) -> CargoResult { } } let has_custom_args = unit.profile.rustc_args.is_some(); - let exec_engine = cx.exec_engine.clone(); let filenames = try!(cx.target_filenames(unit)); let root = cx.out_dir(unit); @@ -240,7 +242,9 @@ fn rustc(cx: &mut Context, unit: &Unit) -> CargoResult { let cwd = cx.config.cwd().to_path_buf(); rustc.args(&try!(cx.rustflags_args(unit))); - + let json_errors = cx.build_config.json_errors; + let package_id = unit.pkg.package_id().clone(); + let target = unit.target.clone(); return Ok(Work::new(move |state| { // Only at runtime have we discovered what the extra -L and -l // arguments are for native libraries, so we process those here. We @@ -266,7 +270,36 @@ fn rustc(cx: &mut Context, unit: &Unit) -> CargoResult { } state.running(&rustc); - try!(exec_engine.exec(rustc).chain_error(|| { + let process_builder = rustc.into_process_builder(); + try!(if json_errors { + #[derive(RustcEncodable)] + struct Message<'a> { + reason: &'a str, + package_id: &'a PackageId, + target: &'a Target, + message: json::Json, + } + process_builder.exec_with_streaming( + &mut |line| assert!(line.is_empty()), + &mut |line| { + let rustc_message = json::Json::from_str(line).unwrap_or_else(|_| { + panic!("Compiler produced invalid json: `{}`", line) + }); + + let message = Message { + reason: "rustc-message", + package_id: &package_id, + target: &target, + message: rustc_message, + }; + let encoded = json::encode(&message).unwrap(); + println!("{}", encoded); + + }, + ).map(|_| ()) + } else { + process_builder.exec() + }.chain_error(|| { human(format!("Could not compile `{}`.", name)) })); @@ -495,6 +528,10 @@ fn build_base_args(cx: &Context, cmd.arg("--color").arg(&color_config.to_string()); } + if cx.build_config.json_errors { + cmd.arg("--error-format").arg("json"); + } + cmd.arg("--crate-name").arg(&unit.target.crate_name()); if !test { diff --git a/src/cargo/ops/mod.rs b/src/cargo/ops/mod.rs index 48edabd94..442d8c5a8 100644 --- a/src/cargo/ops/mod.rs +++ b/src/cargo/ops/mod.rs @@ -1,6 +1,6 @@ pub use self::cargo_clean::{clean, CleanOptions}; pub use self::cargo_compile::{compile, compile_ws, resolve_dependencies, CompileOptions}; -pub use self::cargo_compile::{CompileFilter, CompileMode}; +pub use self::cargo_compile::{CompileFilter, CompileMode, MessageFormat}; pub use self::cargo_read_manifest::{read_manifest,read_package,read_packages}; pub use self::cargo_rustc::{compile_targets, Compilation, Layout, Kind, Unit}; pub use self::cargo_rustc::{Context, LayoutProxy}; diff --git a/tests/build.rs b/tests/build.rs index 565739638..14817b167 100644 --- a/tests/build.rs +++ b/tests/build.rs @@ -2276,6 +2276,81 @@ fn explicit_color_config_is_propagated_to_rustc() { ")); } +#[test] +fn compiler_json_error_format() { + if !is_nightly() { return } + + let p = project("foo") + .file("Cargo.toml", r#" + [project] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + + [dependencies.bar] + path = "bar" + "#) + .file("src/main.rs", "fn main() { let unused = 92; }") + .file("bar/Cargo.toml", r#" + [project] + + name = "bar" + version = "0.5.0" + authors = ["wycats@example.com"] + "#) + .file("bar/src/lib.rs", r#"fn dead() {}"#); + + assert_that(p.cargo_process("build").arg("-v") + .arg("--message-format").arg("json-v1"), + execs().with_json(r#" + { + "reason":"rustc-message", + "package_id":"bar 0.5.0 ([..])", + "target":{"kind":["lib"],"name":"bar","src_path":"[..]lib.rs"}, + "message":{ + "children":[],"code":null,"level":"warning","rendered":null, + "message":"function is never used: `dead`, #[warn(dead_code)] on by default", + "spans":[{ + "byte_end":12,"byte_start":0,"column_end":13,"column_start":1,"expansion":null, + "file_name":"[..]","is_primary":true,"label":null,"line_end":1,"line_start":1, + "suggested_replacement":null, + "text":[{"highlight_end":13,"highlight_start":1,"text":"fn dead() {}"}] + }] + } + } + + { + "reason":"rustc-message", + "package_id":"foo 0.5.0 ([..])", + "target":{"kind":["bin"],"name":"foo","src_path":"[..]main.rs"}, + "message":{ + "children":[],"code":null,"level":"warning","rendered":null, + "message":"unused variable: `unused`, #[warn(unused_variables)] on by default", + "spans":[{ + "byte_end":22,"byte_start":16,"column_end":23,"column_start":17,"expansion":null, + "file_name":"[..]","is_primary":true,"label":null,"line_end":1,"line_start":1, + "suggested_replacement":null, + "text":[{"highlight_end":23,"highlight_start":17,"text":"[..]"}] + }] + } + } +"#)); +} + +#[test] +fn wrong_message_format_option() { + let p = project("foo") + .file("Cargo.toml", &basic_bin_manifest("foo")) + .file("src/main.rs", "fn main() {}"); + p.build(); + + assert_that(p.cargo_process("build").arg("--message-format").arg("XML"), + execs().with_status(101) + .with_stderr_contains("\ +[ERROR] argument for --message-format must be human or json-v1, but found `XML`")); +} + #[test] fn no_warn_about_package_metadata() { let p = project("foo") diff --git a/tests/cargotest/support/mod.rs b/tests/cargotest/support/mod.rs index fb6c7daf6..6de6df197 100644 --- a/tests/cargotest/support/mod.rs +++ b/tests/cargotest/support/mod.rs @@ -258,7 +258,7 @@ pub struct Execs { expect_exit_code: Option, expect_stdout_contains: Vec, expect_stderr_contains: Vec, - expect_json: Option, + expect_json: Option>, } impl Execs { @@ -288,7 +288,9 @@ impl Execs { } pub fn with_json(mut self, expected: &str) -> Execs { - self.expect_json = Some(Json::from_str(expected).unwrap()); + self.expect_json = Some(expected.split("\n\n").map(|obj| { + Json::from_str(obj).unwrap() + }).collect()); self } @@ -324,8 +326,18 @@ impl Execs { &actual.stdout, true)); } - if let Some(ref expect_json) = self.expect_json { - try!(self.match_json(expect_json, &actual.stdout)); + if let Some(ref objects) = self.expect_json { + let lines = match str::from_utf8(&actual.stdout) { + Err(..) => return Err("stdout was not utf8 encoded".to_owned()), + Ok(stdout) => stdout.lines().collect::>(), + }; + if lines.len() != objects.len() { + return Err(format!("expected {} json lines, got {}", + objects.len(), lines.len())); + } + for (obj, line) in objects.iter().zip(lines) { + try!(self.match_json(obj, line)); + } } Ok(()) } @@ -375,14 +387,9 @@ impl Execs { } - fn match_json(&self, expected: &Json, stdout: &[u8]) -> ham::MatchResult { - let stdout = match str::from_utf8(stdout) { - Err(..) => return Err("stdout was not utf8 encoded".to_owned()), - Ok(stdout) => stdout, - }; - - let actual = match Json::from_str(stdout) { - Err(..) => return Err(format!("Invalid json {}", stdout)), + fn match_json(&self, expected: &Json, line: &str) -> ham::MatchResult { + let actual = match Json::from_str(line) { + Err(e) => return Err(format!("invalid json, {}:\n`{}`", e, line)), Ok(actual) => actual, }; -- 2.30.2